package com.springboot.lock.lock_redis.aspect;

import com.springboot.lock.lock_redis.anno.RedisLock;
import com.springboot.lock.lock_redis.lock.DistributeLock;
import com.springboot.lock.lock_redis.lock.RedisDistributeLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.UUID;

/**
 * @Author: TongRui乀
 * @Date: 2020-08-05 19:22
 * @description：
 */
@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    @Autowired
    @Qualifier("redisDistributeLock")
    private DistributeLock distributeLock;

    @Pointcut("@annotation(com.springboot.lock.lock_redis.anno.RedisLock)")
    public void pointCut() {
    }


    @Around("pointCut()")
    public Object lockAspect(ProceedingJoinPoint pj) {

        // 计算 key
        String key = calculateKey(pj);
        // 计算 value
        String value = UUID.randomUUID().toString();
        // 获取锁的释放时间
        RedisLock redisLock = getAnnotation(pj, RedisLock.class);
        // 获取锁
        boolean lock = this.distributeLock.tryLock(key, value, redisLock.expire(), redisLock.waitTime());

        if(!lock){
            return "获取锁失败";
        }
        log.info("获取锁成功:{}", Thread.currentThread().getName());
        // 调用目标方法
        Object proceed = null;
        try {
            proceed = pj.proceed();
        } catch (Throwable throwable) {

            throwable.printStackTrace();
        }finally {
            // 释放锁
            this.distributeLock.unLock(key);
        }
        return proceed;
    }

    /**
     *  解析出注解
     * @param pj
     * @param clazz
     * @param <T>
     * @return
     */
    private<T extends Annotation> T getAnnotation(ProceedingJoinPoint pj, Class<T> clazz){

        MethodSignature signature = (MethodSignature) pj.getSignature();

        Method method = signature.getMethod();

        return method.getAnnotation(clazz);
    }

    private String calculateKey(ProceedingJoinPoint pj) {

        MethodSignature signature = (MethodSignature) pj.getSignature();

        Object[] args = pj.getArgs();

        Method method = signature.getMethod();

        Parameter[] parameters = method.getParameters();

        // 获取到锁注解
        RedisLock redisLock = method.getAnnotation(RedisLock.class);

        if (redisLock == null) {
            throw new RuntimeException("不使用分布式锁");
        }

        String[] columns = redisLock.columns();

        StringBuilder sb = new StringBuilder(method.getName());

        sb.append(":");

        for (int i = 0; i < parameters.length; i++) {
            if (columns[i].equals(parameters[i].getName())) {
                sb.append(args[i]).append(",");
            }
        }

        return sb.toString();
    }

}
